﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.IO;

namespace WindowsFormsApplication1
{
    class PvcSymbolicDynamicsAnalysis
    {
        private double[] _alpha = new double[3];    // parameters for setting the threshold
        private double _beta = 0;                   // adaptative parameter, beta = 0.1 in the paper
        private double _signalThresh = 0;           //
        private int _frequency = 0;

        private int[] _samples;                     // samples to be analysed
        private string _symbolic = "";              // samples extracted symbols

        private int _pvcThresh = 0;
        List<beatSymbolicData> beatSymbolics = new List<beatSymbolicData>();


        public PvcSymbolicDynamicsAnalysis()
        {
            this._frequency = 360; //(MIT-BIH = 360)
            this._beta = 0.1;
            /*
            this._alpha[0] = 0.7;
            this._alpha[1] = 0.3;
            this._alpha[2] = 0.3;
            //*/
            /*
            this._alpha[0] = 0.2;
            this._alpha[1] = 0.2;
            this._alpha[2] = 0.2;
            //*/
            //*
            this._alpha[0] = 0.4;
            this._alpha[1] = 0.2;
            this._alpha[2] = 0.3;
            //*/

        }

        public PvcSymbolicDynamicsAnalysis(int frequency, double beta, double[] alpha)
        {
            this._frequency = frequency;
            this._beta = beta;
            this._alpha = alpha;
        }

        
        public void detectPVC(int[] samples)
        {
            this._samples = samples;

            //call next step
            this.signalFilter();
        }


        public void signalFilter()
        {



            //call next step
            this.symbolSequenceGeneration();
        }

        public void symbolSequenceGeneration()
        {
            int negativeMin = 0;                    // min amplitude
            int positiveMax = 0;                    // max amplitude
            int negativeCount = 0;                  // number of points falling into the band [beta*negativeMin, 0]
            int positiveCount = 0;                  // number of points falling into the band [0, beta*positiveMax]
            int sampleCount = this._samples.Length; // number of samples



            // sample zero offsetting
            int zeroOffset = this._samples[0];
            for (int i = 0; i < sampleCount; i++)
                this._samples[i] -= zeroOffset;

            // pre-threshold statistics settings
            negativeMin = lowestValue(this._samples);
            positiveMax = highestValue(this._samples);

           
            for (int i = 0; i < sampleCount; i++)
            {
                //Console.Write("\n" + "Index:" + i + "  Value:" + this._samples[i]);  
                if ((this._beta * negativeMin <= this._samples[i]) && (this._samples[i] <= 0))      // negative analysis
                    negativeCount++;
                else
                    if ((0 <= this._samples[i]) && (this._samples[i] <= this._beta * positiveMax))  // positive analysis
                        positiveCount++;
            }


            // symbol sequence generation
            if ((negativeCount + positiveCount) > (this._alpha[0] * sampleCount))
                this._signalThresh = this._alpha[1] * positiveMax;
            else
                this._signalThresh = this._alpha[2] * positiveMax;

            for (int i = 0; i < sampleCount; i++)
            {
                if (this._samples[i] > this._signalThresh)
                    this._symbolic += "1";
                else
                    this._symbolic += "0";
            }

            Console.Write("\nNumber_of_Samples:" + sampleCount + "\tThresh:" + this._signalThresh);
            Console.Write("\nNegativeCount:" + negativeCount + "\tNegativeMin:" + negativeMin);  
            Console.Write("\nPositiveCount:" + positiveCount + "\tPositiveMax:" + positiveMax);
            //for (int i = 0; i < sampleCount; i++)
            //    Console.Write("\n" + "index: " + i + " - symbol: " + this._symbolic[i] + " - value: " + this._samples[i]);
        


            // call next step
            this.substringExtraction();
        }
        
        public void substringExtraction()
        {
            beatSymbolicData singleBeatSymbolic = new beatSymbolicData();
            char cNew, cOld = this._symbolic[0];

            // gather information about the 1s series
            for (int i = 1; i < this._symbolic.Length ; i++)
			{
                cNew = this._symbolic[i];

                if (cOld == '1')
                {
                    if (cNew == '0')
                    {
                        singleBeatSymbolic.End = i - 1;
                        singleBeatSymbolic.Width = i - singleBeatSymbolic.Begin;
                        beatSymbolics.Add(singleBeatSymbolic);

                        singleBeatSymbolic = new beatSymbolicData();
                    }
                }
                else //(cOld == '0')
                {
                    if (cNew == '1')
                    {
                        singleBeatSymbolic.Begin = i;
                    }
                }
                cOld = cNew;
            }

            foreach (beatSymbolicData bs in beatSymbolics)
                Console.Write("\n\tWidth: " + bs.Width + "\tIndex: " + bs.Begin ); //+ "("+ this.indexToTime(bs.Begin) +")"); 


            // call next step
            this.pvcBeatDetector();
        }
        
        public void pvcBeatDetector()
        {
            // preparing histogram for cluster search
            Console.Write("\n _________________________");
            Console.Write("\n - Histogram");

            SortedDictionary<int, int> histogram = new SortedDictionary<int, int>();
            foreach (beatSymbolicData bs in beatSymbolics)
            {
                if (bs.Width > 0)
                    if (histogram.ContainsKey(bs.Width))
                        histogram[bs.Width]++;
                    else
                        histogram.Add(bs.Width, 1);
            }
          


            // nearest neighbor clustering

            //copy histogram to more simple structure
            int[][] information = new int[histogram.Count][];

            for (int i = 0; i < histogram.Count; i++)
            {
                
                KeyValuePair<int, int> histogramEntry = histogram.ElementAt(i);
                int[] info = new int[3];
                info[0] = histogramEntry.Key;       // key: width
                info[1] = histogramEntry.Value;     // value: frequency
                info[2] = 0;                        // group: 1=normal, 2=pvc
                information[i] = info;
            }

            //print histogram   
            for (int i = 0; i < information.Length; i++)
                Console.Write("\n\t" + information[i][0] + "\t" + information[i][1] + "\t" + information[i][2]);

            // 2 neighbor clustering
            bool converged = false;
            int idxLeft = 0;
            int idxRight = histogram.Count - 1;
            int idxThresh = 0;
            int oldThresh = -99;

            
            while (!converged)
            {
                // group set of data
                for (int i = idxLeft; i <= idxRight; i++)
                {
                    if ((information[i][0] - information[idxLeft][0]) <= (information[idxRight][0] - information[i][0]))
                    {
                        information[i][2] = 1;  //NORMAL
                        idxThresh = i;
                    }
                    else
                    {
                        information[i][2] = 2;  //PVC
                    }
                }


                int idxNew = 0;

                //set the new left index
                int valLeft = 0;
                for (int i = idxLeft; i <= idxThresh; i++)
                    valLeft += information[i][0];
                valLeft = (int)Math.Truncate(1.0 * valLeft / (idxThresh - idxLeft + 1));

                for (int i = idxLeft; i <= idxThresh; i++)
                    if (information[i][0] <= valLeft)
                        idxNew = i;
                idxLeft = idxNew;


                //set the new right index
                int valRight = 0;
                for (int i = idxThresh + 1; i <= idxRight; i++)
                    valRight += information[i][0];
                valRight = (int)Math.Round(1.0 * valRight / (idxRight - idxThresh), MidpointRounding.AwayFromZero);

                for (int i = idxThresh + 1; i <= idxRight; i++)
                    if (information[i][0] <= valRight)
                        idxNew = i;
                idxRight = idxNew;


                // verify convergence
                if (idxThresh == oldThresh)
                    converged = true;
                else
                    oldThresh = idxThresh;
            }

            //print neighborhood   
            Console.Write("\n _________________________");
            Console.Write("\n - Neighborhoods (1)normal (2)pvc");
            for (int i = 0; i < information.Length; i++)
                Console.Write("\n\t" + information[i][0] + "\t" + information[i][1] + "\t" + information[i][2]);   

            this._pvcThresh = information[idxThresh][0];
                      

            // pvc searching    
            Console.Write("\n _________________________");
            Console.Write("\n - Detecting PVCs ");

            foreach (beatSymbolicData bs in beatSymbolics)
                if (bs.Width > this._pvcThresh)
                    Console.Write("\n\tPVC @ " + bs.Begin); 

            Console.Write("\n _________________________");
            Console.Write("\n - End of SDA analisys");   
        }



        public int lowestValue(int[] data)
        {
            int lowest = int.MaxValue;
            foreach (int value in data)
                if (lowest > value)
                    lowest = value;
            return lowest;
        }

        public int highestValue(int[] data)
        {
            int highest = int.MinValue;
            foreach (int value in data)
                if (highest < value)
                    highest = value;
            return highest;
        }


        //public string indexToTime(int index)
        //{
        //    int div, rem;
        //    string clock = "";
        //    //index = index;

        //    //compute hours
        //    div = this._frequency * 24 * 60;
        //    rem = index;
        //    clock += (int) rem / div + ":";

        //    //compute minutes
        //    div = this._frequency * 60; 
        //    rem = (int) Math.IEEERemainder(index, div);
        //    clock += (int)rem / div + ":";

        //    //compute seconds
        //    div = this._frequency;
        //    rem = (int) Math.IEEERemainder(index, div);
        //    clock += (int)rem / div;
            
        //    return clock;
        //}
    }




    public class beatSymbolicData
    {
        private int begin;
        private int end;
        private int width;
        private int cluster;


        public beatSymbolicData()
        {
            begin = end = width = 0;
        }

        public int Begin
        {
            get { return begin; }
            set { begin = value; }
        }

        public int End
        {
            get { return end; }
            set { end = value; }
        }
       
        public int Width
        {
            get { return width; }
            set { width = value; }
        }

        public int Cluster
        {
            get { return cluster; }
            set { cluster = value; }
        }

    }
}
